package cn.sh.tools.service.component.lock;

import cn.sh.tools.annotation.TimeConsume;
import cn.sh.tools.domain.ShContext;
import cn.sh.tools.util.ThreadLocalUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description
 * @Classname LockComponent
 * @Date 2023/4/28
 * @Author sh
 **/
@Component
@Slf4j
public class LockComponent {

    /**
     * 注解 @Resource 的name取值来源于 Spring boot 的一个配置类
     * @see TaskExecutionAutoConfiguration#applicationTaskExecutor(TaskExecutorBuilder)
     */
    @Resource(name = "applicationTaskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 通过 Java visualVM dump线程快照查看线程状态，主要是分析{@link ReentrantLock#lock()}会让线程状态转换为waiting还是blocked。
     * 当然，通过ReentrantLock#lock()方法的逻辑来看，最终会调用{@link LockSupport#park()}方法，而此方法只会将线程状态变为waiting，而不是blocked。
     *
     * @param createThreadNum 要创建的线程数
     * @param millis          模拟子线程执行实际业务的耗时情况
     * @author youyehaoer
     */
    @TimeConsume
    public void lockStateTest(int createThreadNum, long millis) {
        ShContext shContext = ThreadLocalUtils.getShContext();
        if (Objects.nonNull(shContext)) {
            log.info("id: " + shContext.getId());
        }
        String parentThreadName = Thread.currentThread().getName();
        log.info(parentThreadName +
                "通过 Java visualVM dump线程快照查看线程状态，主要是分析【ReentrantLock#lock()】会让线程状态转换为waiting还是blocked。");
        // 主线程计数器，目的是为了让主线程等待所有子线程执行完成。
        CountDownLatch mainCdl = new CountDownLatch(createThreadNum);
        // 子线程计数器，目的是为了能够让所有子线程在同一时间点开始执行。
        CountDownLatch sunCdl = new CountDownLatch(createThreadNum);
        ReentrantLock reentrantLock = new ReentrantLock();

        // 1-
        for (int i = 0; i < createThreadNum; i++) {
            // 异步执行
            this.threadPoolTaskExecutor.execute(() -> {
                try {
                    // 1-1 每个子线程执行到这里，就在这里等待。一直等到【1-2】执行完成，也就是等到 sunCdl计数器减为0，才会继续向下执行。
                    sunCdl.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                /**
                 * 在高并发情况下，必然会有线程因为获取不到锁而被阻塞。随便摘抄了一个线程的快照信息。
                 * "SH-Thread9" #65 prio=5 os_prio=0 tid=0x000001a7e537b800 nid=0x5fd0 waiting on condition [0x0000006d5abff000]
                 *    java.lang.Thread.State: WAITING (parking)
                 *         at sun.misc.Unsafe.park(Native Method)
                 *         - parking to wait for  <0x0000000718451430> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
                 *         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
                 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
                 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
                 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
                 *         at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
                 *         at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
                 *         at cn.sh.tools.service.component.lock.LockComponent.lambda$lockStateTest$0(LockComponent.java:38)
                 *         at cn.sh.tools.service.component.lock.LockComponent$$Lambda$415/128568340.run(Unknown Source)
                 *         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                 *         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                 *         at java.lang.Thread.run(Thread.java:750)
                 *
                 *    Locked ownable synchronizers:
                 *         - <0x0000000723bc65b0> (a java.util.concurrent.ThreadPoolExecutor$Worker)
                 */
                // 1-3 当【1-2】执行完成的时候，也就是当计数器减为0的时候，所有子线程才会在同一时刻开始执行。在这里也就意味着所有子线程会在同一时刻开始抢占锁资源。
                reentrantLock.lock();
                try {
                    try {
                        // 模拟业务逻辑的耗时
                        Thread.sleep(millis);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log.info("父线程:[{}], 子线程:[{}]", parentThreadName, Thread.currentThread().getName());
                } finally {
                    // 1-4 释放锁资源
                    reentrantLock.unlock();
                    // 1-5 主线程计数器减1
                    mainCdl.countDown();
                }
            });
        }

        // 1-2 这里对应【1-1】，计数器执行减操作，当for循环执行完成的时候（也就是当计数器减为0的时候），所有子线程才会开始【同时】执行业务代码。
        for (int i = 0; i < createThreadNum; i++) {
            sunCdl.countDown();
        }
        log.info("========== 子线程开始并发执行了 ==========");

        try {
            // 2- 让主线程等在这里，等待所有子线程执行完成。
            mainCdl.await();
            log.info("========== 所有子线程都已执行完成 ==========");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 线程阻塞测试
     *
     * @param createThreadNum 要创建的线程数
     * @param capacity        阻塞队列的容量
     * @author youyehaoer
     */
    public void blockingStateTest(int capacity, int createThreadNum) {
        CountDownLatch sunCdl = new CountDownLatch(createThreadNum);

        LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>(capacity);
        for (int i = 0; i < createThreadNum; i++) {
            this.threadPoolTaskExecutor.execute(() -> {
                try {
                    sunCdl.await();
                    /**
                     * 在高并发情况下，必然会有线程因为 阻塞队列已满 而被阻塞。随便摘抄了一个线程的快照信息。
                     * 【
                     *  当然，想要出现这样的情况，那么就要满足两个前提：
                     *      ① 入参 capacity 就要比 createThreadNum 小。
                     *      ② 只有往 阻塞队列添加元素的操作，没有取出的操作。因为如果有取出操作的话，就不容易出现线程被阻塞的情况。
                     * 】
                     * 如果 createThreadNum 比线程池的核心线程数大的话，那么也有可能出现下面的情况，因为线程池也有自己的 工作队列（阻塞队列）。
                     * "SH-Thread8" #64 prio=5 os_prio=0 tid=0x000001a7e537b000 nid=0x6600 waiting on condition [0x0000006d5aafe000]
                     *    java.lang.Thread.State: WAITING (parking)
                     *         at sun.misc.Unsafe.park(Native Method)
                     *         - parking to wait for  <0x0000000724e78960> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
                     *         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
                     *         at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
                     *         at java.util.concurrent.LinkedBlockingQueue.put(LinkedBlockingQueue.java:350)
                     *         at cn.sh.tools.service.component.lock.LockComponent.lambda$blockingStateTest$1(LockComponent.java:76)
                     *         at cn.sh.tools.service.component.lock.LockComponent$$Lambda$416/608099432.run(Unknown Source)
                     *         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                     *         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                     *         at java.lang.Thread.run(Thread.java:750)
                     *
                     *    Locked ownable synchronizers:
                     *         - <0x0000000723bc6180> (a java.util.concurrent.ThreadPoolExecutor$Worker
                     */
                    linkedBlockingQueue.put(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }

        for (int i = 0; i < createThreadNum; i++) {
            sunCdl.countDown();
        }

        log.info("========== 子线程开始并发执行了 ==========");
    }

    /**
     * @param createThreadNum 要创建的线程数。尽量设置个十几二十个，设置的太小可能程序执行太快就发生不了死锁。
     */
    public void deadlockTest(int createThreadNum) {
        CountDownLatch sunCdl = new CountDownLatch(createThreadNum);
        // 1- 填充数据
        List<DeadLockObj> deadLockObjList = new ArrayList<>(createThreadNum);
        /**
         * for循环 创建DeadLockObj对象，这里只创建了两种对象。
         * 这里创建两种对象的原因是为了 后面创建的线程互相持有锁。
         */
        for (int i = 0; i < createThreadNum; i++) {
            if (i % 2 != 0) {
                // i 为 奇数时，给 DeadLockObj 对象的 a = 1，b = 2。
                deadLockObjList.add(new DeadLockObj(i, 1, 2));
            } else {
                // i 为 偶数时，给 DeadLockObj 对象的 a = 2，b = 1。
                deadLockObjList.add(new DeadLockObj(i, 2, 1));
            }
        }
        deadLockObjList.sort(Comparator.comparingInt(DeadLockObj::getId));

        // 循环数据，调用 deadLockInternal()方法，展示死锁情况。
        deadLockObjList.forEach(deadLockObj -> {
            this.threadPoolTaskExecutor.execute(() -> {
                try {
                    sunCdl.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                /**
                 * 在高并发情况下，必然会出现死锁。用 JConsole 查看如下所示：
                 * ------------------------------------------------------------
                 *  名称: SH-Thread1
                 *  状态: java.lang.Integer@2506f34b上的BLOCKED, 拥有者: SH-Thread4
                 *  总阻止数: 2, 总等待数: 1
                 *
                 *  堆栈跟踪:
                 *  cn.sh.tools.service.component.lock.LockComponent.deadLockInternal(LockComponent.java:182)
                 *     - 已锁定 java.lang.Integer@71e4522b
                 *  cn.sh.tools.service.component.lock.LockComponent.lambda$null$2(LockComponent.java:170)
                 *  cn.sh.tools.service.component.lock.LockComponent$$Lambda$459/1128113902.run(Unknown Source)
                 *  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                 *  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                 *  java.lang.Thread.run(Thread.java:750)
                 *  ------------------------------------------------------------
                 *  名称: SH-Thread4
                 *  状态: java.lang.Integer@71e4522b上的BLOCKED, 拥有者: SH-Thread1
                 *  总阻止数: 2, 总等待数: 0
                 *
                 *  堆栈跟踪:
                 *  cn.sh.tools.service.component.lock.LockComponent.deadLockInternal(LockComponent.java:182)
                 *     - 已锁定 java.lang.Integer@2506f34b
                 *  cn.sh.tools.service.component.lock.LockComponent.lambda$null$2(LockComponent.java:170)
                 *  cn.sh.tools.service.component.lock.LockComponent$$Lambda$459/1128113902.run(Unknown Source)
                 *  java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
                 *  java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
                 *  java.lang.Thread.run(Thread.java:750)
                 */
                this.deadLockInternal(deadLockObj);
            });
        });

        for (int i = 0; i < createThreadNum; i++) {
            sunCdl.countDown();
        }
    }

    private void deadLockInternal(DeadLockObj deadLockObj) {
        /**
         * ① 线程A 持有 Integer.valueOf(1)，线程B 持有 Integer.valueOf(2)
         * ② 线程A 抢占 Integer.valueOf(2)，但是2被线程B持有。
         *   同时，线程B 抢占 Integer.valueOf(1)，但是1被线程A持有。
         * 线程A 和 线程B 要抢占的锁都被对方所持有，所以，必然会出现死锁。
         */
        // 第一把锁
        synchronized (Integer.valueOf(deadLockObj.getA())) {
            // 第二把锁
            synchronized (Integer.valueOf(deadLockObj.getB())) {
                log.info(String.valueOf(deadLockObj.getId()));
            }
        }
    }

    @Getter
    @AllArgsConstructor
    static class DeadLockObj {
        Integer id;
        int a;
        int b;
    }

}
